package com.ruoyi.file.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.file.mapper.FilesMapper;
import com.ruoyi.file.model.vo.FileUploadInfo;
import com.ruoyi.file.model.vo.Files;
import com.ruoyi.file.service.ChunkUploadService;
import com.ruoyi.file.utils.MinioUtils;
import com.ruoyi.file.utils.RedisUtil;
import com.ruoyi.file.utils.RespEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Service
public class ChunkChunkUploadServiceImpl implements ChunkUploadService {

    @Resource
    private FilesMapper filesMapper;

    @Resource
    private MinioUtils minioUtils;

    @Resource
    private RedisUtil redisUtil;
    //一天
    @Value("${minio.breakpoint-time}")
    private Integer breakpointTime;

    /**
     * 通过 md5 获取已上传的数据（断点续传）
     *
     * @param md5 String
     * @return Mono<Map < String, Object>>
     */
    @Override
    public R<FileUploadInfo> getByFileMD5(String md5) {

        log.info("tip message: 通过 <{}> 查询redis是否存在", md5);
        // 从redis获取文件名称和id
        FileUploadInfo fileUploadInfo = (FileUploadInfo) redisUtil.get(md5);
        if (fileUploadInfo != null) {
            // 正在上传，查询上传后的分片数据
            List<Integer> chunkList = minioUtils.getChunkByFileMD5(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), fileUploadInfo.getFileType());
            fileUploadInfo.setChunkUploadedList(chunkList);
            fileUploadInfo.setStatus(RespEnum.UPLOADING.getCode());
            return R.ok(fileUploadInfo);

        }

        log.info("tip message: 通过 <{}> 查询mysql是否存在", md5);
        // 查询数据库是否上传成功
        Files one = filesMapper.selectByFileMd5(md5);
        if (one != null) {
            FileUploadInfo mysqlsFileUploadInfo = new FileUploadInfo();
            BeanUtils.copyProperties(one, mysqlsFileUploadInfo);
            mysqlsFileUploadInfo.setStatus(RespEnum.UPLOAD_SUCCESSFUL.getCode());
            return R.ok(mysqlsFileUploadInfo);
        }
        FileUploadInfo nofileUploadInfo = new FileUploadInfo();
        nofileUploadInfo.setStatus(RespEnum.NOT_UPLOADED.getCode());
        return R.ok(nofileUploadInfo);
    }


    /**
     * 文件分片上传
     *
     * @param fileUploadInfo 文件
     * @return Mono<Map < String, Object>>
     */
    @Override
    public Map<String, Object> initMultiPartUpload(FileUploadInfo fileUploadInfo) {

        FileUploadInfo redisFileUploadInfo = (FileUploadInfo) redisUtil.get(fileUploadInfo.getFileMd5());
        if (redisFileUploadInfo != null) {
            fileUploadInfo = redisFileUploadInfo;
        }
        log.info("tip message: 通过 <{}> 开始初始化<分片上传>任务", fileUploadInfo);
        // 获取桶
        String bucketName = minioUtils.getBucketName(fileUploadInfo.getFileType());

        // 单文件上传
        if (fileUploadInfo.getChunkNum() == 1) {
            log.info("tip message: 当前分片数量 <{}> 进行单文件上传", fileUploadInfo.getChunkNum());
            saveFileToDB(fileUploadInfo);
            return minioUtils.getUploadObjectUrl(fileUploadInfo.getFileName(), bucketName);
        }
        // 分片上传
        else {
            log.info("tip message: 当前分片数量 <{}> 进行分片上传", fileUploadInfo.getChunkNum());
            Map<String, Object> map = minioUtils.initMultiPartUpload(fileUploadInfo, fileUploadInfo.getFileName(), fileUploadInfo.getChunkNum(), fileUploadInfo.getContentType(), bucketName);
            String uploadId = (String) map.get("uploadId");
            fileUploadInfo.setUploadId(uploadId);
            redisUtil.set(fileUploadInfo.getFileMd5(), fileUploadInfo, breakpointTime * 60 * 60 * 24);
            return map;
        }
    }


    /**
     * 文件合并
     *
     * @param fileUploadInfo fileUploadInfo
     * @return String
     */
    @Override
    @Transactional
    public String mergeMultipartUpload(FileUploadInfo fileUploadInfo) {
        log.info("tip message: 通过 <{}> 开始合并<分片上传>任务", fileUploadInfo);
        FileUploadInfo redisFileUploadInfo = (FileUploadInfo) redisUtil.get(fileUploadInfo.getFileMd5());
        if (redisFileUploadInfo != null) {
            fileUploadInfo.setFileName(redisFileUploadInfo.getFileName());
        }
        boolean result = minioUtils.mergeMultipartUpload(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), fileUploadInfo.getFileType());

        //合并成功
        if (result) {
            //存入数据库
            Files files = saveFileToDB(fileUploadInfo);
            redisUtil.del(fileUploadInfo.getFileMd5());
            return files.getUrl();
        }
        return null;
    }

    @Override
    public String getFilePath(String bucketName, String fileName) {
        return minioUtils.getFilePath(bucketName, fileName);
    }

    @Override
    public String upload(MultipartFile file, String bucketName) {
        minioUtils.upload(file, bucketName);
        return getFilePath(bucketName, file.getName());
    }

    @Override
    public Map<String, Object> secondPass(String fileMD5) {
        Files files = filesMapper.selectOne(new QueryWrapper<Files>().eq("file_md5", fileMD5));
        if (files == null) {
            return null;
        }
        if (files.getUrl() != null && Objects.equals(files.getChunkNum(), files.getUploadChunkNum())) {
            //秒传
            HashMap<String, Object> map = new HashMap<>();
            map.put("url", files.getUrl());
            map.put("fileName", files.getFileName());
            return map;
        }
        return null;
    }

    private Files saveFileToDB(FileUploadInfo fileUploadInfo) {
        String url = this.getFilePath(fileUploadInfo.getFileType().toLowerCase(), fileUploadInfo.getFileName());
        //存入数据库
        Files files = new Files();
        BeanUtils.copyProperties(fileUploadInfo, files);
        files.setBucketName(fileUploadInfo.getFileType());
        files.setUploadChunkNum(fileUploadInfo.getChunkNum());
        files.setUrl(url);
        filesMapper.insertXml(files);
        return files;
    }
}